<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018\12\5 0005
 * Time: 16:07
 */

namespace console\controllers;

use common\helpers\Encryption;
use Swoole\Coroutine\Redis;
use yii\console\Controller;
use yii\db\Query;

class WsController extends Controller
{

    const WS_IP       = "0.0.0.0";
    const WS_PORT     = 9501;
    const WS_LOG_PATH = '/usr/local/nginx/logs/swoole.log';

    const PROCESS_NAME = 'DH_WS';
    // TODO 需根据服务器配置调整
    const WORKER_NUM      = 2;
    const TASK_WORKER_NUM = 2;

    const HEARTBEAT_CHECK_INTERVAL = 5;
    const HEARTBEAT_IDLE_TIME      = 600;

    const REDIS_HOST  = '127.0.0.1';
    const REDIS_PORT  = 6379;
    const REDIS_PWD   = '';
    const REDIS_INDEX = 1;

    //消息类型
    const MSG_TYPE_OPEN          = 1;            // ws客户端连接消息
    const MSG_TYPE_SEND          = 2;            // ws客户端发送消息
    const MSG_TYPE_IDENTITY      = 3;            // ws客户端身份消息
    const MSG_TYPE_ONLINE_NUMBER = 4;            // 服务器发送给客户端  在线人数消息
    const MSG_TYPE_DROP_LINE     = 5;            // 服务器发送给客户端， 掉线消息
    const MSG_TYPE_FILE          = 6;            // 文件消息
    const MSG_TYPE_CLOSE         = 7;            // 客服关闭会话的消息
    const MSG_TYPE_HISTORY       = 8;            // 获取历史记录
    const MSG_TYPE_TRANSFER      = 9;            // 转接  客服
    const MSG_TYPE_NEED_LOGIN    = 10;           // 需要登录
    const MSG_TYPE_NOTICE        = 11;           // 通知类消息
    const MSG_TYPE_ORDER         = 12;           // 历史订单消息
    const MSG_TYPE_WORKORDER     = 13;           // 历史工单消息

    //连接用户的身份
    const NORMAL_IDENTITY         = 1; //游客
    const VIP_IDENTITY            = 2; //登录后的会员
    const CUSTOMER_PRE_IDENTITY   = 3; //售前客服
    const CUSTOMER_AFTER_IDENTITY = 4; //售后客服

    //连接用户列表
    const GUEST_LIST          = 'guest_list';
    const VIP_LIST            = "vip_list";
    const CUSTOMER_PRE_LIST   = "customer_pre_list_";
    const CUSTOMER_AFTER_LIST = "customer_after_list_";

    //任务池  类型
    const TASK_ADD_MSG       = "add_msg";
    const TASK_ADD_IDENTITY  = "add_identity";
    const TASK_DEL_IDENTITY  = "del_identity";
    const TASK_CLOSE_SESSION = "close_session";
    const TASK_COUNT_TIMES   = 'count_times';

    //todo table 大小  （后期无法扩容，谨慎设置）
    const TABLE_SIZE = 1024;
    //单条消息不能超过1K
    const MESSAGE_MAX_LEN = 1024;

    //在线人数  类型
    const ONLINE_USER  = 1;              // 用户在线总人数
    const ONLINE_GUEST = 2;              // 游客在线人数
    const ONLINE_VIP   = 3;              // 会员在线人数

    //finish 的类型
    const FINISH_GET_ONLINE = 1;
    const FINISH_DROP_LINE  = 2;

    //会话发起类型
    const SESSION_GUEST_TO_CUSTOMER = 1;      //游客对客服发起
    const SESSION_VIP_TO_CUSTOMER   = 2;      //会员对客服发起
    const SESSION_CUSTOMER_TO_GUEST = 3;      //客服对游客发起
    const SESSION_CUSTOMER_TO_VIP   = 4;      //客服对会员发起
    const SESSION_VIP_TO_GUEST      = 5;      //会员对游客发起
    const SESSION_GUEST_TO_VIP      = 6;      //游客对会员发起

    //文字提示
    const CHARACTER_DESCRIPTION_CLOSE = '客服关闭了会话';
    const CHARACTER_DESCRIPTION_LOGIN = '请先登录';

    const ENCRYPT_KEY = 'dh-sq-ws-20181210';

    const TABLE_CHAT        = 'chat';
    const TABLE_CHAT_COUNTS = 'chat_counts';
    const TABLE_SESSIONS    = 'sessions';
    const TABLE_BEARER      = 'gb_crm_bearer';

    private $swoole_client;

    // 控制台应用方法
    public function actionRun()
    {
        $this->clearOnline();

        $this->swoole_client = new \swoole_websocket_server(self::WS_IP, self::WS_PORT);

        //配置
        $this->swoole_client->set([
            'worker_num'               => self::WORKER_NUM,
            'task_worker_num'          => self::TASK_WORKER_NUM,
            'heartbeat_check_interval' => self::HEARTBEAT_CHECK_INTERVAL,
            'heartbeat_idle_time'      => self::HEARTBEAT_IDLE_TIME,
            'log_file'                 => self::WS_LOG_PATH,
        ]);

        $table = new \swoole_table(self::TABLE_SIZE);
        $table->column('fd', \swoole_table::TYPE_INT, 8);
        $table->column('type', \swoole_table::TYPE_INT);
        $table->column('avatar', \swoole_table::TYPE_STRING, 255);
        $table->column('nickname', \swoole_table::TYPE_STRING, 64);
        $table->column('ip', \swoole_table::TYPE_STRING, 20);
        $table->column('userId', \swoole_table::TYPE_INT, 8);
        $table->column('companyId', \swoole_table::TYPE_INT, 8);
        $table->create();
        $this->swoole_client->table = $table;

        $this->swoole_client->on('handshake', [$this, 'onHandshake']);

        $this->swoole_client->on('open', [$this, 'onOpen']);

        $this->swoole_client->on('task', [$this, 'onTask']);

        $this->swoole_client->on('finish', [$this, 'onFinish']);

        $this->swoole_client->on('message', [$this, 'onMessage']);

        $this->swoole_client->on('close', [$this, 'onClose']);

        $this->swoole_client->on('start', [$this, 'onStart']);

        $this->swoole_client->start();
    }

    public function onStart()
    {
        \swoole_set_process_name(self::PROCESS_NAME);    //设置进程名
    }

    public function onHandshake(\swoole_http_request $request, \swoole_http_response $response)
    {
        $get_params = $request->get;
        if (!isset($get_params['identity']) || $this->check_identity($get_params['identity']) === false) {
            $response->end();
            return false;
        }
        // websocket握手连接算法验证
        $secWebSocketKey = $request->header['sec-websocket-key'];
        $patten          = '#^[+/0-9A-Za-z]{21}[AQgw]==$#';
        if (0 === preg_match($patten, $secWebSocketKey) || 16 !== strlen(base64_decode($secWebSocketKey))) {
            $response->end();
            return false;
        }
        echo $request->header['sec-websocket-key'];
        $key = base64_encode(sha1(
            $request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
            true
        ));

        $headers = [
            'Upgrade'               => 'websocket',
            'Connection'            => 'Upgrade',
            'Sec-WebSocket-Accept'  => $key,
            'Sec-WebSocket-Version' => '13',
        ];

        // WebSocket connection to 'ws://127.0.0.1:9502/'
        // failed: Error during WebSocket handshake:
        // Response must not include 'Sec-WebSocket-Protocol' header if not present in request: websocket
        if (isset($request->header['sec-websocket-protocol'])) {
            $headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol'];
        }

        foreach ($headers as $key => $val) {
            $response->header($key, $val);
        }

        $response->status(101);
        $response->end();
        $this->onOpen($this->swoole_client, $request);
        return true;
    }

    /**
     * 客户端关闭
     * @param $server
     * @param $fd
     */
    public function onClose($server, $fd)
    {
        $data = [
            'fd'        => $fd,
            'task_type' => self::TASK_DEL_IDENTITY,
        ];
        $server->task(serialize($data));
        echo "client -- {$fd} closed\n";
    }

    /**
     * 处理客户端连接后的操作
     * @param $server
     * @param $request
     */
    public function onOpen($server, $request)
    {
        echo "server: handshake success with fd{$request->fd}\n";
    }

    /**
     * 处理客户端的消息
     * @param $server
     * @param $frame
     */
    public function onMessage($server, $frame)
    {
        $requestData = json_decode($frame->data, true);

        $data = $this->format_response_data($server, $requestData, $frame->fd);

        if (isset($data['type']) && $data['type'] == self::MSG_TYPE_NEED_LOGIN) {
            $server->push(intval($frame->fd), json_encode($data));
            $this->onClose($server, $frame->fd);
        } else {
            $server->push(intval($data['userFd']), json_encode($data));
        }
    }

    /**
     * 处理任务池
     * @param $serv
     * @param $taskId
     * @param $workerId
     * @param $data
     * @throws \MongoDB\Driver\Exception\Exception
     */
    public function onTask($serv, $taskId, $workerId, $data)
    {
        $req = unserialize($data);
        if ($req) {
            switch ($req['task_type']) {
                case self::TASK_ADD_MSG:
                    $this->add_msg($req['userFd'], $req['toUserFd'], $req['msg'], $req['chat_id']);
                    break;
                case self::TASK_ADD_IDENTITY:
                    unset($req['task_type']);
                    $this->setIdentity($serv, $req['fd'], $req);
                    $finish_data = [
                        'type' => self::FINISH_GET_ONLINE,
                    ];
                    $serv->finish(serialize($finish_data));
                    break;
                case self::TASK_DEL_IDENTITY:
                    $user_info = $this->getUser($req['fd']);
                    $this->delIdentity($serv, $req['fd'], $user_info['type'], $user_info['companyId']);
                    if ($user_info['type'] == self::NORMAL_IDENTITY || $user_info['type'] == self::VIP_IDENTITY) {
                        $finish_data = [
                            'type' => self::FINISH_DROP_LINE,
                            'fd'   => $req['fd'],
                        ];
                        $serv->finish(serialize($finish_data));
                    }
                    break;
                case self::TASK_COUNT_TIMES:
                    $info = $this->swoole_client->table->get((string)$req['fd']);
                    $this->setReceptionCounts($info['userId']);
                    break;
                default:
                    break;
            }
        }
    }

    /**
     * 执行task后调用
     * @param $server
     * @param $task_id
     * @param $data
     */
    public function onFinish($server, $task_id, $data)
    {
        $req = unserialize($data);
        switch ($req['type']) {
            case self::FINISH_GET_ONLINE:
                $number         = $this->getNumbers(self::ONLINE_USER);
                $broadcast_data = [
                    'type'   => self::MSG_TYPE_ONLINE_NUMBER,
                    'number' => $number
                ];
                $this->broadcast($server, $broadcast_data);
                break;
            case self::FINISH_DROP_LINE:
                $number         = $this->getNumbers(self::ONLINE_USER);
                $broadcast_data = [
                    'type'   => self::MSG_TYPE_DROP_LINE,
                    'number' => $number,
                    'fd'     => $req['fd'],
                ];
                $this->broadcast($server, $broadcast_data);
                break;
            default:
                break;
        }
    }

    /**
     * 暂未使用
     * @param $server
     * @param $fd
     * @param $code
     * @param $data
     */
    public function sendErrorMsg($server, $fd, $code, $data)
    {
        sendJson($server, $fd, ['type' => $code, 'msg' => $data]);
    }

    /**
     * 发送数据给客户端(暂未使用)
     * @param $server
     * @param $fd
     * @param $data
     */
    public function sendJson($server, $fd, $data)
    {
        $msg = json_encode($data);
        if ($server->push(intval($fd), $msg) === false) {
            $server->close(intval($fd));
        }
    }

    /**
     * 设置连接用户的身份
     * @param $type
     */
    private function setIdentity($server, $fd, $data)
    {
        $type_list = $this->typeToList($data['type'], $data['companyId']);
        $redis     = new \Redis();
        $redis->connect(self::REDIS_HOST, self::REDIS_PORT);
        $redis->select(self::REDIS_INDEX);
        $redis->sAdd($type_list, $fd);
        $redis->close();
        $this->swoole_client->table->set((string)$fd, $data);
        if ($data['type'] == self::CUSTOMER_PRE_IDENTITY || $data['type'] == self::CUSTOMER_AFTER_IDENTITY)
            $server->protect($fd, true);
    }

    /**
     * 客户端关闭后   删除连接信息
     * @param $server
     * @param $fd
     */
    private function delIdentity($server, $fd, $type, $company_id)
    {
        $type_list = $this->typeToList($type, $company_id);
        $redis     = new \Redis();
        $redis->connect(self::REDIS_HOST, self::REDIS_PORT);
        $redis->select(self::REDIS_INDEX);
        $redis->sRem($type_list, $fd);
        $redis->close();
        $this->swoole_client->table->del((string)$fd);
    }

    /**
     * 根据用户的type, 获取用户列表的类型;
     * @param $type
     * @return string
     */
    private function typeToList($type, $company_id)
    {
        switch ($type) {
            case self::VIP_IDENTITY:
                $type_list = self::VIP_LIST;
                break;
            case self::CUSTOMER_PRE_IDENTITY:
                $type_list = self::CUSTOMER_PRE_LIST . $company_id;
                break;
            case self::CUSTOMER_AFTER_IDENTITY:
                $type_list = self::CUSTOMER_AFTER_LIST . $company_id;
                break;
            case self::NORMAL_IDENTITY:
                $type_list = self::GUEST_LIST;
                break;
            default:
                $type_list = self::GUEST_LIST;
        }
        return $type_list;
    }

    /**
     * todo 暂未处理售前客服 和 售后客服
     * 获取一个客服
     * @param $type
     * @return array|string
     */
    private function getCustomerOne($type, $company_id)
    {
        if ($type == self::NORMAL_IDENTITY || $type == self::VIP_IDENTITY) {
            $redis = new \Redis();
            $redis->connect(self::REDIS_HOST, self::REDIS_PORT);
            $redis->select(self::REDIS_INDEX);
            $fd = $redis->sRandMember(self::CUSTOMER_PRE_LIST . $company_id);
            $redis->close();
            return $fd ? $fd : '';
        }
        return '';
    }

    /**
     * *获取在线人数
     * @param $type
     * @return int
     */
    private function getNumbers($type)
    {
        $redis = new \Redis();
        $redis->connect(self::REDIS_HOST, self::REDIS_PORT);
        $redis->select(self::REDIS_INDEX);
        switch ($type) {
            case self::ONLINE_USER:
                $number = $redis->sCard(self::VIP_LIST) + $redis->sCard(self::GUEST_LIST);
                break;
            case self::ONLINE_GUEST:
                $number = $redis->sCard(self::GUEST_LIST);
                break;
            case self::ONLINE_VIP:
                $number = $redis->sCard(self::VIP_LIST);
                break;
            default:
                $number = 0;
        }
        $redis->close();
        return $number;
    }

    /**
     * 获取聊天记录
     * @param $chat_id
     * @param int $page
     * @return mixed
     */
    private function getHistory($chat_id, $page = 1)
    {
        $limit     = 10;
        $start_row = ($page - 1) * $limit;
        $res       = (new Query())->from(self::TABLE_CHAT)->where(['session_id' => $chat_id])->limit($start_row, $limit)->all();
        return $res;
    }


    /**
     * 获取单个用户信息
     * @param $fd
     * @return mixed
     */
    private function getUser($fd)
    {
        return $this->swoole_client->table->get((string)$fd);
    }

    /**
     * 根据fd获取在线用户的类型
     * @param $fd
     */
    private function fdToType($server, $fd)
    {
        return $this->swoole_client->table->get((string)$fd, 'type');
        //return $info['type'];
    }

    /**
     * 广播消息给客服
     * @param $server
     * @param $data
     */
    private function broadcast($server, $data)
    {
        $redis = new \swoole_redis();
        $redis->connect(self::REDIS_HOST, self::REDIS_PORT, function (\swoole_redis $redis, $result) use ($server, $data) {
            $redis->select(self::REDIS_INDEX, function (\swoole_redis $redis, $result) use ($server, $data) {
                $redis->sMembers(self::CUSTOMER_PRE_LIST, function (\swoole_redis $redis, $result) use ($server, $data) {
                    $redis->close();
                    if ($result) {
                        foreach ($result as $v) {
                            $server->push($v, json_encode($data));
                        }
                    }
                });
            });
        });
    }

    /**
     * 存储消息
     * @param $userFd
     * @param $toUserFd
     * @param $msg
     */
    private function add_msg($userFd, $toUserFd, $msg, $chat_id)
    {
        $data = [
            'userFd'     => $userFd,
            'toUserFd'   => $toUserFd,
            'msg'        => htmlspecialchars($msg),
            'create_at'  => time(),
            'session_id' => $chat_id,
        ];
        \Yii::$app->db->createCommand()->insert(self::TABLE_CHAT, $data)->execute();
    }

    /**
     * 验证登录用户的身份（游客不用）
     */
    private function check_identity($user_info)
    {
        if (!$user_info)
            return false;
        $user_data = json_decode(Encryption::base64_decrypt($user_info, self::ENCRYPT_KEY), true);
        if (is_array($user_data) && isset($user_data['user_id'])) {
            return $user_data;
        } else {
            return false;
        }
    }

    /**
     * 添加会话记录
     * @param $customerFd //客服唯一标志
     * @param $customerId //客服ID
     * @param $userFd //用户唯一标志
     * @param $username // 用户昵称
     * @param $session_initiator //会话发起方
     * @param $session_close //会话关闭方
     * @param $session_source // 会话来源
     * @return string
     * @throws \yii\db\Exception
     */
    private function add_session($customerFd, $customerId, $userFd, $username, $session_initiator, $session_close, $session_source)
    {

        $info = (new Query())->from(self::TABLE_SESSIONS)->where(['username' => $username])->one();
        if ($info)
            return $info['id'];
        $bearer_id = $this->add_bearer();
        $data = [
            'bearer_id'         => $bearer_id,
            'create_at'         => time(),
            'customer_id'       => $customerId,
            'session_start_at'  => time(),
            'session_initiator' => $session_initiator,
            'session_close'     => $session_close,
            'session_source'    => $session_source,
            'user_ip'           => $this->swoole_client->table->get($userFd, 'ip'),
            'status'            => 1,
            'customer_fd'       => $customerFd,
            'user_fd'           => $userFd,
            'username'          => $username,
        ];
        \Yii::$app->db->createCommand()->insert(self::TABLE_SESSIONS, $data)->execute();
        return \Yii::$app->db->getLastInsertID();
    }

    /**
     * 格式化  响应的数据
     * @param $server
     * @param $requestData
     * @param $fd
     * @return array
     */
    private function format_response_data($server, $requestData, $fd)
    {
        switch ($requestData['type']) {
            case self::MSG_TYPE_IDENTITY:
                $user_info = isset($requestData['user_info']) ? $requestData['user_info'] : '';
                $user_data = $this->check_identity($user_info);
                if ($user_data === false) {
                    $data = [
                        'userFd'   => $requestData['userFd'],
                        'toUserFd' => $requestData['toUserFd'],
                        'type'     => self::MSG_TYPE_NEED_LOGIN,
                        'msg'      => self::CHARACTER_DESCRIPTION_LOGIN,
                    ];
                } else {
                    $client_info = $server->getClientInfo($fd);
                    $company_id  = isset($user_data['company_id']) ? $user_data['company_id'] : 0;
                    //todo 用户信息
                    $user_info = [
                        'fd'        => $fd,
                        'type'      => $requestData['identity_type'],
                        'task_type' => self::TASK_ADD_IDENTITY,
                        'avatar'    => $user_data['avatar'],
                        'nickname'  => $user_data['nickname'],
                        'ip'        => $client_info['remote_ip'],
                        'userId'    => $user_data['user_id'],
                        'companyId' => $company_id,
                    ];
                    $server->task(serialize($user_info));
                    $toUserFd = $this->getCustomerOne($user_info['type'], $company_id);
                    if ($toUserFd) {
                        $count_data = [
                            'task_type' => self::TASK_COUNT_TIMES,
                            'fd'        => $toUserFd,
                        ];
                        $server->task(serialize($count_data));
                    }
                    if (in_array($user_info['type'], [self::NORMAL_IDENTITY, self::VIP_IDENTITY])) {
                        //TODO  身份信息需处理
                        $chat_id = $this->add_session($toUserFd, 1, $fd, 'lxl', 1, 1, 1);
                    } else {
                        $chat_id = '';
                    }

                    $data = [
                        'userFd'   => $fd,
                        'toUserFd' => $toUserFd,
                        'chat_id'  => $chat_id,
                        'type'     => self::MSG_TYPE_IDENTITY,
                    ];
                }
                break;
            case self::MSG_TYPE_SEND:
                try {
                    $task_data = [
                        'userFd'    => $requestData['userFd'],
                        'toUserFd'  => $requestData['toUserFd'],
                        'task_type' => self::TASK_ADD_MSG,
                        'msg'       => $requestData['msg'],
                        'msg_type'  => $requestData['type'],
                        'chat_id'   => $requestData['chat_id'],
                    ];
                    $server->taskwait(serialize($task_data));
                } catch (Exception $e) {

                }
                $data = [
                    'userFd'   => $requestData['toUserFd'],
                    'toUserFd' => $requestData['userFd'],
                    'type'     => self::MSG_TYPE_SEND,
                    'msg'      => $requestData['msg'],
                    'chat_id'  => $requestData['chat_id'],
                ];
                break;
            case self::MSG_TYPE_FILE:
                try {
                    $task_data = [
                        'userFd'    => $requestData['userFd'],
                        'toUserFd'  => $requestData['toUserFd'],
                        'task_type' => self::TASK_ADD_MSG,
                        'msg'       => $requestData['msg'],
                        'url'       => $requestData['url'],
                        'msg_type'  => $requestData['type'],
                    ];
                    $server->taskwait(serialize($task_data));
                } catch (Exception $e) {

                }
                $data = [
                    'userFd'   => $requestData['toUserFd'],
                    'toUserFd' => $requestData['userFd'],
                    'type'     => self::MSG_TYPE_FILE,
                    'msg'      => $requestData['msg'],
                    'url'      => $requestData['url'],
                ];
                break;
            case self::MSG_TYPE_CLOSE:
                // TODO 需处理会话记录
                $data = [
                    'userFd'   => $requestData['toUserFd'],
                    'toUserFd' => $requestData['userFd'],
                    'type'     => self::MSG_TYPE_CLOSE,
                    'msg'      => self::CHARACTER_DESCRIPTION_CLOSE,
                ];
                break;
            default:
                $data = [];
                break;
        }
        return $data;
    }

    /**
     * 获取在线客服列表
     */
    private function getOnlineCustomer($company_id)
    {
        $redis = new Redis();
        $redis->connect(self::REDIS_HOST, self::REDIS_PORT);
        $redis->select(self::REDIS_INDEX);
        $pre   = $redis->sMembers(self::CUSTOMER_PRE_LIST . $company_id);
        $after = $redis->sMembers(self::CUSTOMER_AFTER_LIST . $company_id);
        $redis->close();
        return ['pre_list' => $pre, 'after_list' => $after];
    }

    /**
     * 查询历史订单
     */
    private function getHistoryOrders($fd)
    {
        $user = $this->getUser((string)$fd);
        if ($user['type'] == self::NORMAL_IDENTITY) {

        } else {

        }
    }

    /**
     * 获取工单信息
     */
    private function getWorkOrders()
    {

    }

    /**
     * 获取历史会话记录
     * @param $customerId
     */
    private function getHistorySession($customerId)
    {

    }

    /**
     *设置客服 接待次数
     */
    private function setReceptionCounts($userId)
    {
        $info = (new Query())->from(self::TABLE_CHAT_COUNTS)->where(['user_id' => $userId, 'format_date' => date('Ymd')])->one();
        if ($info) {
            \Yii::$app->db->createCommand()->update(self::TABLE_CHAT_COUNTS, ['counts' => $info['counts'] + 1], 'id = ' . $info['id'])->execute();
        } else {
            \Yii::$app->db->createCommand()->insert(self::TABLE_CHAT_COUNTS, [
                'user_id'      => $userId,
                'format_date'  => date('Ymd'),
                'format_month' => date('Ym'),
                'counts'       => 1
            ])->execute();
        }
    }

    /**
     * 转接客服
     * @param $customerFd //要转接的客服
     * @param $userFd //要转接的用户
     */
    private function transferCustomer($server, $customerFd, $userFd)
    {
        // TODO 移到异步的外面
        if (!$server->exist($customerFd)) {
            //客服不在线
        }
        if (!$server->exist($userFd)) {
            //用户不在线
        }
        $data = [
            'userFd'   => $userFd,
            'toUserFd' => $customerFd,
            'type'     => self::MSG_TYPE_TRANSFER,
        ];
        $server->push($userFd, json_encode($data));
        $server->push($userFd, json_encode(['type' => self::MSG_TYPE_TRANSFER]));
    }

    /**
     * 清除在线  信息
     */
    private function clearOnline()
    {
        $redis = new \Redis();
        $redis->connect(self::REDIS_HOST, self::REDIS_PORT);
        $redis->select(self::REDIS_INDEX);
        $pre_list_keys   = $redis->keys(self::CUSTOMER_PRE_LIST . "*");
        $after_list_keys = $redis->keys(self::CUSTOMER_AFTER_LIST . "*");
        if ($pre_list_keys)
            $redis->del($pre_list_keys);
        if ($after_list_keys)
            $redis->del($after_list_keys);
        $redis->del(self::VIP_LIST);
        $redis->del(self::GUEST_LIST);
        $redis->close();
    }

    /**
     * 添加来访记录
     * @param $fd
     * @return string
     * @throws \yii\db\Exception
     */
    private function add_bearer($fd)
    {
        $user = $this->getUser((string)$fd);
        $info = (new Query())->from(self::TABLE_BEARER)->where(['user_id' => $user['user_id']])->one();
        if ($info)
            return $info['id'];
        $data = [
            'user_id'     => $user['user_id'],
            'user_type'   => $user['type'],
            'nickname'    => $user['nickname'],
            'create_time' => time(),
        ];
        \Yii::$app->db->createCommand()->insert(self::TABLE_BEARER, $data)->execute();
        return \Yii::$app->db->getLastInsertID();
    }
}